﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;

namespace TJDevHouse.Starcraft.Game
{
	/// <summary>
	/// Holds all the data for an individual game, such as the map, the forces (teams of players),
	/// general game state, etc.
	/// </summary>
	public class Game
	{
		/// <summary>
		/// The map the game is using
		/// </summary>
		public Map Map { get; private set; }

		/// <summary>
		/// Information about each type of unit
		/// </summary>
		public Dictionary<UnitType, UnitTypeSpec> UnitTypeSpecs { get; private set; }

		/// <summary>
		/// The forces (teams) in the game.
		/// </summary>
		public List<Force> Forces { get; private set; }

		/// <summary>
		/// All the players in the game
		/// </summary>
		public Dictionary<int, Player> Players { get; private set; }

		/// <summary>
		/// The player the client is controlling.
		/// </summary>
		public Player ClientPlayer { get; set; }

		/// <summary>
		/// The current frame number
		/// </summary>
		public int FrameNumber { get; private set; }

		/// <summary>
		/// All known units in the game.
		/// 
		/// Currently stores all units seen at any point, whether they still exist in the game or not.
		/// TODO remove dead units from this list as best as possible
		/// </summary>
		public Dictionary<int, Unit> Units { get; private set; }

		/// <summary>
		/// All units currently visible in the game.
		/// </summary>
		public Dictionary<int, Unit> VisibleUnits { get; private set; }

		/// <summary>
		/// Collection of units stored by player ID.
		/// </summary>
		public Dictionary<int, Dictionary<int, Unit>> PlayerUnits { get; private set; }


		//constants for import array indexes
		private const int PLYR_DATA_ID = 0;
		private const int PLYR_DATA_MINERALS = 1;
		private const int PLYR_DATA_GAS = 2;
		private const int PLYR_DATA_SUPPLY_TOTAL = 3;
		private const int PLYR_DATA_SUPPLY_USED = 4;

		private const int UNIT_DATA_ID = 0;
		private const int UNIT_DATA_PLAYER_ID = 1;
		private const int UNIT_DATA_TYPE_ID = 2;
		private const int UNIT_DATA_X = 3;
		private const int UNIT_DATA_Y = 4;
		private const int UNIT_DATA_HP = 5;
		private const int UNIT_DATA_SHIELDS = 6;
		private const int UNIT_DATA_ENERGY = 7;
		private const int UNIT_DATA_ORDER_ID = 8;
		private const int UNIT_DATA_RESOURCES = 9;
		private const int UNIT_DATA_STATUS_BM = 10;

		//unit status bit mask values
		private const int UNIT_STATUS_BEING_CONSTRUCTED = 1;
		private const int UNIT_STATUS_BLIND = 2;
		private const int UNIT_STATUS_BURROWED = 4;
		private const int UNIT_STATUS_CARRYING_GAS = 8;
		private const int UNIT_STATUS_CARRYING_MINERALS = 16;
		private const int UNIT_STATUS_CLOAKED = 32;
		private const int UNIT_STATUS_CONSTRUCTING = 64;
		private const int UNIT_STATUS_GATHERING_GAS = 128;
		private const int UNIT_STATUS_GATHERING_MINERALS = 256;
		private const int UNIT_STATUS_REPAIRING = 512;
		private const int UNIT_STATUS_RESEARCHING = 1024;
		private const int UNIT_STATUS_SIEGED = 2048;
		private const int UNIT_STATUS_TRAINING = 4096;
		private const int UNIT_STATUS_UNPOWERED = 8192;
		private const int UNIT_STATUS_UPGRADING = 16384;

		/// <summary>
		/// Setup a game by providing the very minimum amount of data needed,
		/// 
		/// Ensure to add forces to the game.
		/// 
		/// TODO add the upgrade and tech type data in here
		/// </summary>
		/// <param name="map"></param>
		/// <param name="unitTypes"></param>
		public Game(Map map, Dictionary<UnitType, UnitTypeSpec> unitTypes)
		{
			Map = map;
			UnitTypeSpecs = unitTypes;
			Players = new Dictionary<int, Player>();
			Forces = new List<Force>();

			Units = new Dictionary<int, Unit>();
			VisibleUnits = new Dictionary<int, Unit>();
			PlayerUnits = new Dictionary<int, Dictionary<int, Unit>>();
			
		}

		/// <summary>
		/// Update the game state.
		/// 
		/// Data is passed in a fairly raw format in an attempt to avoid repeated processing
		/// of the same data.
		/// 
		/// Player data should be in the following format:
		///		player_id.minerals.gas.supply_total.supply_used
		///		
		/// Unit data should be in the following format:
		///		unit_id.player_id.type_id.x.y.hp.shields.energy.order_id.resources.status_bit_mask
		/// </summary>
		public void UpdateGame(int frameNum, string[] playerData, string[] unitData)
		{
			FrameNumber = frameNum;

			VisibleUnits.Clear();

			UpdatePlayers(playerData);
			UpdateUnits(unitData);
		}

		/// <summary>
		/// Update the player states from the update data
		/// </summary>
		/// <param name="rawPlayerData"></param>
		private void UpdatePlayers(string[] rawPlayerData)
		{
			//player_id.minerals.gas.supply_total.supply_used
			foreach (string pData in rawPlayerData)
			{
				string[] pBits = pData.Split('.');

				//don't bother to update if we don't have any data on player (i.e. enemy)
				if (pBits[PLYR_DATA_SUPPLY_TOTAL] != "0")
				{
					int id = int.Parse(pBits[PLYR_DATA_ID]);
					int minerals = int.Parse(pBits[PLYR_DATA_MINERALS]);
					int gas = int.Parse(pBits[PLYR_DATA_GAS]);
					int supplyTotal = int.Parse(pBits[PLYR_DATA_SUPPLY_TOTAL]);
					int supplyUsed = int.Parse(pBits[PLYR_DATA_SUPPLY_USED]);

					Player player = Players[id];

					player.Minerals = minerals;
					player.VespeneGas = gas;
					player.SupplyTotal = supplyTotal;
					player.SupplyUsed = supplyUsed;
				}
			}
		}

		/// <summary>
		/// Update the unit states from the supplied update data
		/// </summary>
		/// <param name="rawUnitData"></param>
		private void UpdateUnits(string[] rawUnitData)
		{
			//unit_id.player_id.type_id.x.y.hp.shields.energy.order_id.resources.status_bit_mask
			foreach (string uData in rawUnitData)
			{
				string[] uBits = uData.Split('.');

				int id = int.Parse(uBits[UNIT_DATA_ID]);
				int playerID = int.Parse(uBits[UNIT_DATA_PLAYER_ID]);
				int typeID = int.Parse(uBits[UNIT_DATA_TYPE_ID]);
				int x = int.Parse(uBits[UNIT_DATA_X]);
				int y = int.Parse(uBits[UNIT_DATA_Y]);
				int hp = int.Parse(uBits[UNIT_DATA_HP]);
				int shields = int.Parse(uBits[UNIT_DATA_SHIELDS]);
				int energy = int.Parse(uBits[UNIT_DATA_ENERGY]);
				int orderID = int.Parse(uBits[UNIT_DATA_ORDER_ID]);
				int resources = int.Parse(uBits[UNIT_DATA_RESOURCES]);
				int statusBM = int.Parse(uBits[UNIT_DATA_STATUS_BM]);

				Player player = Players[playerID];

				//only create a new unit if it doesn't currently exist
				Unit u;
				if (!Units.ContainsKey(id))
				{
					u = new Unit(id);
					u.Game = this;
					u.Player = player;
					Units.Add(id, u);
					PlayerUnits[u.Player.ID].Add(u.ID, u);
				}
				else
				{
					u = Units[id];

					//check if the unit has changed player (renegade unit)
					if (u.Player.ID != playerID)
					{
						//has changed player - remove from the original player's units
						PlayerUnits[u.Player.ID].Remove(u.ID);

						u.Player = player;
					}
				}

				VisibleUnits.Add(u.ID, u);

				//update/set the unit's state
				u.LastSeenFrameNumber = FrameNumber;
				u.UnitType = (UnitType)typeID;
				u.PixelCoord.X = x;
				u.PixelCoord.Y = y;
				u.HitPoints = hp;
				u.Shields = shields;
				u.Energy = energy;
				u.Order = (Order)orderID;
				u.Resources = resources;

				UpdateUnitStatusFromBitMask(u, ref statusBM);
			}
		}

		/// <summary>
		/// Takes a unit and sets its various status flags based on the supplied bit mask.
		/// 
		/// See Unit constants for bitmask values.
		/// </summary>
		/// <param name="u"></param>
		/// <param name="bitmask"></param>
		private void UpdateUnitStatusFromBitMask(Unit u, ref int bitmask)
		{
			u.ClearStatuses();
			if (IsBitmaskSet(bitmask, UNIT_STATUS_BEING_CONSTRUCTED))
			{
				u.IsBeingConstructed = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_BLIND))
			{
				u.IsBlind = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_BURROWED))
			{
				u.IsBurrowed = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_CARRYING_GAS))
			{
				u.IsCarryingGas = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_CARRYING_MINERALS))
			{
				u.IsCarryingMinerals = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_CLOAKED))
			{
				u.IsCloaked = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_CONSTRUCTING))
			{
				u.IsConstructing = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_GATHERING_GAS))
			{
				u.IsGatheringGas = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_GATHERING_MINERALS))
			{
				u.IsGatheringMinerals = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_REPAIRING))
			{
				u.IsRepairing = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_RESEARCHING))
			{
				u.IsResearching = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_SIEGED))
			{
				u.IsSieged = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_TRAINING))
			{
				u.IsTraining = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_UNPOWERED))
			{
				u.IsUnpowered = true;
			}
			if (IsBitmaskSet(bitmask, UNIT_STATUS_UPGRADING))
			{
				u.IsUpgrading = true;
			}
		}

		/// <summary>
		/// Checks whether the specified bit is set in the bitmask.
		/// </summary>
		/// <param name="bitmask"></param>
		/// <param name="bit"></param>
		/// <returns></returns>
		private bool IsBitmaskSet(int bitmask, int bit)
		{
			return (bitmask & bit) == bit;
		}

		/// <summary>
		/// Add a force to the game.
		/// </summary>
		/// <param name="force"></param>
		public void AddForce(Force force)
		{
			force.Game = this;

			if (!Forces.Contains(force))
			{
				Forces.Add(force);

				//add any players in the force
				foreach (Player player in force.Players.Values)
				{
					if (!Players.ContainsKey(player.ID))
					{
						Players.Add(player.ID, player);
						PlayerUnits[player.ID] = new Dictionary<int, Unit>();
					}
				}
			}
		}

	}
}
